﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using SysColor = System.Drawing.Color;
using SysRectangle = System.Drawing.Rectangle;
using System.Threading;

namespace PO_2011
{
    public class PNM_Reader : PNM_Data
    {
        public PNM_Reader(string filename)
        {
            if (File.Exists(filename))
            {
                FileStream stream = new FileStream(filename, FileMode.Open);
                this.FromStream(stream);
                stream.Close();
            }
            else
            {
                throw new FileNotFoundException("Plik " + filename + " nie istnieje", filename);
            }
        }

        private void FromStream(Stream stream)
        {
            this.header = new ImagePackageInfo();
            this.binReader = new BinaryReader(stream);

            try
            {
                this.getImageInfo();
                this.setSizeOfBytesPerType();

                this.imageData = new byte[this.header.Width * this.header.Height * this.bytesPerPixel];

                if (this.header.MagicNumber == "P1" || this.header.MagicNumber == "P2" || this.header.MagicNumber == "P3") // ASCII
                {
                    this.createArrayFromText();
                    ReorderRGBtoBGR();

                    createBitmap(ref this.bitmap, this.Header.Width, this.Header.Height);
                }
                else // binarnie.
                {
                    int bytesLeft = (int)(this.binReader.BaseStream.Length - this.binReader.BaseStream.Position);
                    this.imageData = this.binReader.ReadBytes(bytesLeft);
                    if (this.header.MagicNumber == "P4")
                    {
                        this.imageData = this.createBytesToBits(this.imageData);
                    }
                    ReorderRGBtoBGR();

                    createBitmap(ref this.bitmap, this.Header.Width, this.Header.Height);
                }

                Graphics gr = Graphics.FromImage(this.bitmap);
                gr.DrawImage(this.bitmap, 0, 0, this.Header.Width, this.Header.Height);
                gr.Dispose();
            }

            catch (EndOfStreamException e)
            {
                throw new Exception("Błąd podczas odczytu ", e);
            }
            catch (Exception ex)
            {
                throw new Exception("Błąd podczas odczytu ", ex);
            }
            finally
            {
                this.binReader.Close();
            }
        }

        private int ReadValue(BinaryReader binReader)
        {
            string value = string.Empty;
            while (!Char.IsWhiteSpace((char)binReader.PeekChar()))
            {
                value += binReader.ReadChar().ToString();
            }
            binReader.ReadByte();   // usuwa spacje
            return int.Parse(value);
        }

        private void createBitmap(ref Bitmap bitmap, int widthBitmap, int HeightBitmap)
        {
            bitmap = new Bitmap(widthBitmap, HeightBitmap, PixelFormat.Format24bppRgb);

            BitmapData bd = bitmap.LockBits(new Rectangle(0, 0, widthBitmap, HeightBitmap), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
            Marshal.Copy(this.imageData, 0, bd.Scan0, this.imageData.Length);

            bitmap.UnlockBits(bd);
        }

        // naglowek, rozmiar, odcienie 
        private void getImageInfo()
        {
            int headerItemCount = 0;
            
            while (headerItemCount < 4)
            {
                char nextChar = (char)this.binReader.PeekChar();
                if (nextChar == '#')
                {
                    while (this.binReader.ReadChar() != '\n') ;
                }
                else if (Char.IsWhiteSpace(nextChar))
                {
                    this.binReader.ReadChar();
                }
                else
                {
                    switch (headerItemCount)
                    {
                        case 0:
                            char[] chars = this.binReader.ReadChars(2);
                            this.header.MagicNumber = chars[0].ToString() + chars[1].ToString();
                            headerItemCount++;
                            break;
                        case 1:
                            this.header.Width = ReadValue(this.binReader);
                            headerItemCount++;
                            break;
                        case 2:
                            this.header.Height = ReadValue(this.binReader);
                            headerItemCount++;
                            break;
                        case 3:
                            if (this.header.MagicNumber == "P1" | this.header.MagicNumber == "P4")
                            {
                                headerItemCount++;
                            }
                            else
                            {
                                this.header.Depth = ReadValue(this.binReader);
                                headerItemCount++;
                            }
                            break;
                        default:
                            throw new Exception("Error parsing the file header.");
                    }
                }
            }
        }

        private void createArrayFromText()
        {
            int charsLeft = (int)(this.binReader.BaseStream.Length - this.binReader.BaseStream.Position);
            char[] charData = this.binReader.ReadChars(charsLeft);
            string valueString = string.Empty;
            index = 0;
            
            for (int i = 0; i < charData.Length; i++)
            {
                if (Char.IsWhiteSpace(charData[i]))
                {
                    if (valueString != string.Empty)
                    {
                        if (this.header.MagicNumber == "P1")
                        {
                            for (int j = 0; j < valueString.Length; j++)
                            {
                                this.imageData[index] = valueString[j] == '1' ? (byte) 0 : (byte) 255;
                                index++;
                            }
                            index--;
                        }
                        else
                        {
                            this.imageData[index] = (byte)int.Parse(valueString);
                        }
                        valueString = string.Empty;
                        index++;
                    }
                }
                else
                {
                    valueString += charData[i];
                }
            }
        }

        private void setSizeOfBytesPerType()
        {
            switch (this.header.MagicNumber)
            {
                case "P1":
                    this.pixelFormat = PixelFormat.Format8bppIndexed;
                    this.bytesPerPixel = 1;
                    break;
                case "P2":
                    this.pixelFormat = PixelFormat.Format8bppIndexed;
                    this.bytesPerPixel = 1;
                    break;
                case "P3":
                    this.pixelFormat = PixelFormat.Format24bppRgb;
                    this.bytesPerPixel = 3;
                    break;
                case "P4":
                    this.pixelFormat = PixelFormat.Format8bppIndexed;
                    this.bytesPerPixel = 1;
                    break;
                case "P5":
                    this.pixelFormat = PixelFormat.Format8bppIndexed;
                    this.bytesPerPixel = 1;
                    break;
                case "P6":
                    this.pixelFormat = PixelFormat.Format24bppRgb;
                    this.bytesPerPixel = 3;
                    break;
                default:
                    throw new Exception("Unknown Magic Number: " + this.header.MagicNumber);
            }
        }

        private void ReorderRGBtoBGR()
        {
            byte[] tempData;
            int changer = 2, jump = 0;
            
            if (this.header.MagicNumber == "P3" | this.header.MagicNumber == "P6")
            {
                tempData = new byte[this.imageData.Length + (this.imageData.Length / 3)];
            }
            else
            {
                tempData = new byte[4 * this.imageData.Length];
            }

            for (int i = 0; i < this.imageData.Length; i++)
            {
                if (this.header.MagicNumber == "P3" | this.header.MagicNumber == "P6")
                {
                    tempData[i + changer + jump] = this.imageData[i];
                    
                    if(changer <= -2){ 
                        changer = 2;
                        jump++;
                    }
                    else{
                        changer -= 2;
                    }
                }
                else
                {
                    tempData[4 * i] = this.imageData[i];
                    tempData[4 * i + 1] = this.imageData[i];
                    tempData[4 * i + 2] = this.imageData[i];
                    tempData[4 * i + 3] = 100;
                }
            }

            this.imageData = tempData;
        }

        public byte[] createBytesToBits(byte[] img)
        {
            string img_bytes = null;
            byte[] newImageData = new byte[this.header.Width * this.header.Height];
            for (int i = 0; i < img.Length; i++)
            {
                img_bytes = Convert.ToString(img[i], 2);
                
                if (img_bytes.Length < 8)
                {
                    img_bytes = this.FillBits(img_bytes);
                }

                for (int j = 0; j < img_bytes.Length; j++)
                {
                    newImageData[i * 8 + j] = img_bytes[j] == '1' ? (byte) 0 : (byte) 255;
                }
            }
            int ij = 0;
            ij++;
            return newImageData;
        }

        private String FillBits(String str)
        {
            int l = 8 - str.Length;

            for (int i = 0; i < l; i++)
            {
                str = "0" + str;
            }

            return str;
        }

    }
}
